// modified from barebone promise from promisejs.org
function $P_asap(task) {
    events.listen({type: "timeout", timeout: 0}, task);
}

function $P_noop() {
}

let LAST_ERROR = null;

function $P_getThen(obj) {
    try {
        return obj.then;
    } catch (ex) {
        LAST_ERROR = ex;
        return IS_ERROR;
    }
}

function $P_tryCallOne(fn, a) {
    try {
        return fn(a);
    } catch (ex) {
        console.log("promise exception", ex);
        LAST_ERROR = ex;
        return IS_ERROR;
    }
}

function $P_resolvePromise(value) {
    if (value instanceof $P_Promise) return value;

    if (value === null) return NULL;
    if (value === undefined) return UNDEFINED;
    if (value === true) return TRUE;
    if (value === false) return FALSE;
    if (value === 0) return ZERO;
    if (value === '') return EMPTYSTRING;

    if (typeof value === 'object' || typeof value === 'function') {
        try {
            let then = value.then;
            if (typeof then === 'function') {
                return new $P_Promise(then.bind(value));
            }
        } catch (ex) {
            return new $P_Promise(function $P_resolvePromiseF0(resolve, reject) {
                reject(ex);
            });
        }
    }
    return $P_valuePromise(value);
};


function $P_allPromise(arr) {
    const args = Array.prototype.slice.call(arr);

    return new $P_Promise(function $P_allPromisePPF0(resolve, reject) {
        if (args.length === 0) return resolve([]);
        let remaining = args.length;

        function $P_allPPRes(i, val) {
            if (val && (typeof val === 'object' || typeof val === 'function')) {
                if (val instanceof $P_Promise && val.then === $P_Promise.prototype.then) {
                    while (val._state === 3) {
                        val = val._value;
                    }
                    if (val._state === 1) return $P_allPPRes(i, val._value);
                    if (val._state === 2) reject(val._value);
                    val.then(function $P_allPPResF2(val) {
                        $P_allPPRes(i, val);
                    }, reject);
                    return;
                } else {
                    let then = val.then;
                    if (typeof then === 'function') {
                        const p = new $P_Promise(then.bind(val));
                        p.then(function $P_allPPResF1(val) {
                            $P_allPPRes(i, val);
                        }, reject);
                        return;
                    }
                }
            }
            args[i] = val;
            if (--remaining === 0) {
                resolve(args);
            }
        }

        for (var i = 0; i < args.length; i++) {
            $P_allPPRes(i, args[i]);
        }
    });
};

function $P_rejectPromise(value) {
    return new $P_Promise(function $P_rejectPromiseF1(resolve, reject) {
        reject(value);
    });
};

function $P_racePromise(values) {
    return new $P_Promise(function $P_racePromiseF1(resolve, reject) {
        values.forEach(function $P_racePromiseF2(value) {
            $P_Promise.resolve(value).then(resolve, reject);
        });
    });
};


function $P_donePromise(onFulfilled, onRejected) {
    const self = arguments.length ? self.then.apply(self, arguments) : self;
    self.then(null, function $P_donePromiseF1(err) {
        setTimeout(function $P_donePromiseF2() {
            throw err;
        }, 0);
    });
};

function $P_handle(self, deferred) {
    while (self._state === 3) {
        self = self._value;
    }
    if ($P_Promise._onHandle) {
        $P_Promise._onHandle(self);
    }
    if (self._state === 0) {
        if (self._deferredState === 0) {
            self._deferredState = 1;
            self._deferreds = deferred;
            return;
        }
        if (self._deferredState === 1) {
            self._deferredState = 2;
            self._deferreds = [self._deferreds, deferred];
            return;
        }
        self._deferreds.push(deferred);
        return;
    }
    $P_handleResolved(self, deferred);
}

function $P_handleResolved(self, deferred) {
    $P_asap(function $P_handleResolvedF0() {
        const cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
        if (cb === null) {
            if (self._state === 1) {
                $P_resolve(deferred.promise, self._value);
            } else {
                $P_reject(deferred.promise, self._value);
            }
            return;
        }
        const ret = $P_tryCallOne(cb, self._value);
        console.log(LAST_ERROR);
        if (ret === IS_ERROR) {
            $P_reject(deferred.promise, LAST_ERROR);
        } else {
            $P_resolve(deferred.promise, ret);
        }
    });
}

function $P_resolve(self, newValue) {
    // Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure
    console.log("resolve:", newValue)
    if (newValue === self) {
        return $P_reject(
            self,
            new TypeError('A promise cannot be resolved with itself.')
        );
    }
    if (
        newValue &&
        (typeof newValue === 'object' || typeof newValue === 'function')
    ) {
        let then = $P_getThen(newValue);
        if (then === IS_ERROR) {
            return $P_reject(self, LAST_ERROR);
        }
        if (
            then === self.then &&
            newValue instanceof $P_Promise
        ) {
            self._state = 3;
            self._value = newValue;
            $P_finale(self);
            return;
        } else if (typeof then === 'function') {
            $P_doResolve(then.bind(newValue), self);
            return;
        }
    }
    self._state = 1;
    self._value = newValue;
    $P_finale(self);
}

function $P_reject(self, newValue) {
    console.log("$P_reject:",newValue);
    self._state = 2;
    self._value = newValue;
    if ($P_Promise._onReject) {
        $P_Promise._onReject(self, newValue);
    }
    $P_finale(self);
}

function $P_finale(self) {
    if (self._deferredState === 1) {
        $P_handle(self, self._deferreds);
        self._deferreds = null;
    }
    if (self._deferredState === 2) {
        for (let i = 0; i < self._deferreds.length; i++) {
            $P_handle(self, self._deferreds[i]);
        }
        self._deferreds = null;
    }
}

function $P_Handler(onFulfilled, onRejected, promise) {
    this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
    this.onRejected = typeof onRejected === 'function' ? onRejected : null;
    this.promise = promise;
}

function $P_doResolve(fn, promise) {
    let done = false;
    let error = false;
    try {
        fn(function $P_doResolveF0(value) {
            if (done) return;
            done = true;
            $P_resolve(promise, value);
        }, function $P_doResolveF1(reason) {
            if (done) return;
            done = true;
            $P_reject(promise, reason);
        });
    } catch (ex) {
        LAST_ERROR = ex;
        console.log("logic promise exception",ex);
        error = true;
    }
    if (error) {
        done = true;
        $P_reject(promise, LAST_ERROR);
    }
}

function $P_Promise(fn) {
    if (typeof this !== 'object') {
        throw new TypeError('Promises must be constructed via new');
    }
    if (typeof fn !== 'function') {
        throw new TypeError('Promise constructor\'s argument is not a function');
    }
    this._deferredState = 0;
    this._state = 0;
    this._value = null;
    this._deferreds = null;
    this._isPromise=true;

    let self=this;
    this.then = function $P_thenPromise(onFulfilled, onRejected) {
        const res = new $P_Promise($P_noop);
        $P_handle(self, new $P_Handler(onFulfilled, onRejected, res));
        return res;
    };
    this["catch"]=function $P_catchPromise(onRejected) {
        return self.then(null, onRejected);
    };

    if (fn === $P_noop) {
        return;
    }

    $P_doResolve(fn, this);
}
$P_Promise._onHandle = null;
$P_Promise._onReject = null;
$P_Promise._noop = $P_noop;
$P_Promise.resolve = $P_resolvePromise;
$P_Promise.all = $P_allPromise;
$P_Promise.reject = $P_rejectPromise;
$P_Promise.done =$P_donePromise;
$P_Promise.race = $P_racePromise;


const IS_ERROR = {};
const TRUE = $P_valuePromise(true);
const FALSE = $P_valuePromise(false);
const NULL = $P_valuePromise(null);
const UNDEFINED = $P_valuePromise(undefined);
const ZERO = $P_valuePromise(0);
const EMPTYSTRING = $P_valuePromise('');

function $P_valuePromise(value) {
    const p = new $P_Promise($P_Promise._noop);
    p._state = 1;
    p._value = value;
    return p;
}

export default $P_Promise;
